package org.jflame.commons.json.jackson;

import org.jflame.commons.json.JsonCfg;
import org.jflame.commons.json.JsonException;
import org.jflame.commons.json.Jsons;
import org.jflame.commons.util.IOHelper;
import org.jflame.commons.util.StringHelper;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.json.JsonMapper.Builder;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
import com.fasterxml.jackson.databind.ser.SerializerFactory;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;

/**
 * 基于jackson实现json工具类
 * 
 * @author charles.zhang
 */
public class Jacksons implements Jsons {

    private ObjectMapper objMapper;// 同一个ObjectMapper实例部分配置使用后再修改不生效
    // private JsonCfg cfg;

    public Jacksons() {
        this(new JsonCfg());
    }

    /**
     * 构造函数
     * 
     * @param enableAutoTypeSupport 启用自动类型支持
     */
    public Jacksons(JsonCfg cfg) {
        configure(cfg);
    }

    public Jacksons(ObjectMapper mapper) {
        objMapper = mapper;
    }

    @Override
    public String toJson(Object obj) {
        try {
            return objMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new JsonException(e);
        }
    }

    /**
     * Java对象序列化为JSON字符串,按属性组过滤.
     * <p>
     * 配合注解@JsonView使用,如:@JsonView(Views.Default.class)
     * 
     * @param obj 要序列化的Java对象
     * @param viewClass 表示属性组的class,即注解@JsonView中的class
     * @return
     */
    public String toJsonView(Object obj, Class<?> viewClass) {
        try {
            return objMapper.writerWithView(viewClass)
                    .writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new JsonException(e);
        }
    }

    /**
     * Java对象序列化为JSON字符串,指定过滤属性,支持包含或排除模式
     * 
     * @param obj 要序列化的Java对象
     * @param isInclude true=包含属性,false=排除属性
     * @param properties 要过滤的属性
     * @return
     */
    public String toJsonFilter(Object obj, boolean isInclude, String[] properties) {
        SerializerFactory serializerFactory = BeanSerializerFactory.instance
                .withSerializerModifier(new FilterBeanSerializerModifier(properties));
        ObjectMapper newMapper = objMapper.copy()
                .setSerializerFactory(serializerFactory);
        try {
            return newMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public byte[] toJsonBytes(Object obj) {
        try {
            return objMapper.writeValueAsBytes(obj);
        } catch (JsonProcessingException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public void writeJSONString(OutputStream os, Object object) throws IOException {
        try {
            objMapper.writeValue(os, object);
        } finally {
            IOHelper.closeQuietly(os);
        }
    }

    @Override
    public <T> T parseObject(String jsonStr, Class<T> clazz) {
        try {
            return objMapper.readValue(jsonStr, clazz);
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

    public <T> T parseObject(String jsonStr, TypeReference<T> typeRef) {
        try {
            return objMapper.readValue(jsonStr, typeRef);
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T parseObject(String jsonStr, Type type) {
        try {
            return (T) objMapper.readValue(jsonStr, objMapper.getTypeFactory()
                    .constructType(type));
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

    @Override
    public <T> T parseObject(InputStream inStream, Charset charset, Class<T> clazz) throws IOException {
        if (charset == null) {
            return objMapper.readValue(inStream, clazz);
        } else {
            InputStreamReader inReader = new InputStreamReader(inStream, charset);
            return objMapper.readValue(inReader, clazz);
        }
    }

    @Override
    public <T> T parseObject(InputStream inStream, Charset charset, Type type) throws IOException {

        if (charset == null) {
            return objMapper.readValue(inStream, objMapper.getTypeFactory()
                    .constructType(type));
        } else {
            InputStreamReader inReader = new InputStreamReader(inStream, charset);
            return objMapper.readValue(inReader, objMapper.getTypeFactory()
                    .constructType(type));
        }
    }

    @Override
    public <T> List<T> parseList(String jsonStr, Class<T> elementClazz) {
        try {
            CollectionType collType = objMapper.getTypeFactory()
                    .constructCollectionType(List.class, elementClazz);
            return objMapper.readValue(jsonStr, collType);
        } catch (IOException e) {
            throw new JsonException(e);
        }

    }

    @Override
    public <K,V> Map<K,V> parseMap(String jsonStr, Class<K> keyClazz, Class<V> valueClazz) {
        try {
            MapType mapType = objMapper.getTypeFactory()
                    .constructMapType(HashMap.class, keyClazz, valueClazz);
            return objMapper.readValue(jsonStr, mapType);
        } catch (IOException e) {
            throw new JsonException(e);
        }
    }

    public ObjectMapper getObjectMapper() {
        return objMapper;
    }

    /**
     * 启用
     * 
     * @param first
     * @param other
     * @return
     */
    public Jsons enable(SerializationFeature first, SerializationFeature... other) {
        objMapper.enable(first, other);
        return this;
    }

    public Jsons enable(Feature... feature) {
        objMapper.enable(feature);
        return this;
    }

    public Jsons disable(SerializationFeature first, SerializationFeature... other) {
        objMapper.disable(first, other);
        return this;
    }

    public Jsons disable(Feature... feature) {
        objMapper.disable(feature);
        return this;
    }

    /**
     * 启用日期按格式输出,并指定日期格式
     * 
     * @param datePattern 日期格式
     */
    /*@Override
    public void dateFormat(String datePattern) {
        if (StringHelper.isNotEmpty(datePattern)) {
            objMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            objMapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
            objMapper.setDateFormat(new SimpleDateFormat(datePattern));
            objMapper.configOverride(LocalDateTime.class)
                    .setFormat(JsonFormat.Value.forPattern(datePattern));
            objMapper.configOverride(LocalDate.class)
                    .setFormat(JsonFormat.Value.forPattern(datePattern));
        }
    }*/

    /* @Override
    public void ignoreNull(boolean isIgnoreNull) {
        if (isIgnoreNull) {
            // 同一objMapper对象修改此配置不生效需要复制一个
            objMapper = objMapper.setDefaultPropertyInclusion(Include.NON_NULL)
                    .copy();
        } else {
            objMapper = objMapper.setDefaultPropertyInclusion(Include.ALWAYS)
                    .copy();
        }
    }
    
    @Override
    public void prettyPrint() {
        objMapper.enable(SerializationFeature.INDENT_OUTPUT);
    }*/

    @Override
    public Object parseNode(String jsonStr) {
        return parse(jsonStr);
    }

    public JsonNode parse(String jsonStr) {
        try {
            return objMapper.readTree(jsonStr);
        } catch (JsonProcessingException e) {
            throw new JsonException(e);
        }
    }

    public void setPropertyNamingStrategy(int strategy) {
        switch (strategy) {
            case 1:
                objMapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE);
                break;
            case 2:
                objMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);
                break;
            case 3:
                objMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
                break;
            case 4:
                objMapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE);
                break;
            default:
                break;
        }

    }

    public void configure(JsonCfg cfg) {
        Builder builder = JsonMapper.builder()
                .findAndAddModules()// 自动注册存在的模块,包括JDK8类型
                .configure(Feature.IGNORE_UNKNOWN, true)
                .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
                .configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true)// 忽略@Transient或transient修改符成员
                .disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .configure(Feature.WRITE_BIGDECIMAL_AS_PLAIN, cfg.isBigDecimalAsPlain())
                .configure(SerializationFeature.INDENT_OUTPUT, cfg.isPrettyFormat());
        if (cfg.isIgnoreNull()) {
            builder.serializationInclusion(Include.NON_NULL);
        }
        if (cfg.getTimeZone() != null) {
            builder.defaultTimeZone(cfg.getTimeZone());
        }
        if (StringHelper.isNotEmpty(cfg.getDateFormat())) {
            builder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            builder.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
            builder.defaultDateFormat(new SimpleDateFormat(cfg.getDateFormat()));
        }

        if (cfg.getBrowserCompatible()) {
            SimpleModule sm = new SimpleModule();
            sm.addSerializer(Long.class, new BigNumberSerializer(Long.class));
            sm.addSerializer(BigInteger.class, new BigNumberSerializer(BigInteger.class));
            builder.addModule(sm);
        }

        if (cfg.getNamingStrategy() != null) {
            setPropertyNamingStrategy(cfg.getNamingStrategy());
        }
        synchronized (this) {
            if (cfg.isEnableAutoTypeSupport()) {
                objMapper = builder
                        .activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
                        .build();
            } else {
                objMapper = builder.build();
            }
            if (StringHelper.isNotEmpty(cfg.getDateFormat())) {
                // 覆盖JDK8时间格式
                objMapper.configOverride(LocalDateTime.class)
                        .setFormat(JsonFormat.Value.forPattern(cfg.getDateFormat()));
            }
        }

    }

    /**
     * 配置一个自定义注解使得jackson能识别和处理,参数serializerClass和deserializerClass至少传一个
     * <p>
     * 作用和在自定义注解类加上@JacksonAnnotationsInside一样
     * 
     * @param objectMapper
     * @param customAnnotation 注解类
     * @param serializerClass 处理该注解的序列化类
     * @param deserializerClass 处理该注解的反序列化类
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static ObjectMapper configureCustomAnnotation(ObjectMapper objectMapper,
            Class<? extends Annotation> customAnnotation, Class<? extends JsonSerializer> serializerClass,
            Class<? extends JsonDeserializer> deserializerClass) {
        // 根据已有的配置创建自定义的ObjectMapper
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig()
                .getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里，AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector,
                new CustomAnnotationIntrospector(customAnnotation, serializerClass, deserializerClass));
        objectMapper.setAnnotationIntrospector(pair);
        return objectMapper;
    }

    @SuppressWarnings("rawtypes")
    public static ObjectMapper configureCustomAnnotation(ObjectMapper objectMapper,
            Class<? extends Annotation> customAnnotation, Class<? extends JsonSerializer> serializerClass) {
        // 根据已有的配置创建自定义的ObjectMapper
        AnnotationIntrospector annotationIntrospector = objectMapper.getSerializationConfig()
                .getAnnotationIntrospector();
        // 将自定义注解內省器加入到Jackson注解内省器集合里，AnnotationIntrospector是双向链表结构
        AnnotationIntrospector pair = AnnotationIntrospectorPair.pair(annotationIntrospector,
                new CustomAnnotationIntrospector(customAnnotation, serializerClass));
        objectMapper.setAnnotationIntrospector(pair);
        return objectMapper;
    }

}
